SuperStrict
NoDebug
  
Rem  
bbdoc: MaxMod.MP3
End Rem
Module MaxMod.MP3
ModuleInfo "MaxMod: MP3 - v1.25"
ModuleInfo "Author: REDi - Cliff Harman"
ModuleInfo "License: MIT"

?win32
	
	Import MaxMod.Music
	Import BRL.Retro
	Import BRL.AudioSample
	Import PUB.Win32
	
	Include "acm.bmx"
	
	Const MPEG25			:Int = %00
	Const MPEGreserved 		:Int = %01
	Const MPEG2 			:Int = %10
	Const MPEG1 			:Int = %11 
	
	Const LayerReserved 	:Int = %00
	Const LayerIII 			:Int = %01
	Const LayerII			:Int = %10
	Const LayerI			:Int = %11
	
	MaxModLoader.Create(MaxMod_Loader_MP3,"ISO/MPEG Layer3 ~q.mp3~q","mp3")
	
	Function MaxMod_Loader_MP3:TMusic(Url:Object)
		Return TMP3Stream.Load(Url)
	EndFunction


	Type TAudioSampleLoaderMP3 Extends TAudioSampleLoader
		Method LoadAudioSample:TAudioSample( stream:TStream )
			Local Music:TMusic = TMP3Stream.Load(stream)
			If Not music Return Null
			Local Length:Int = Music.Geti(MM_LENGTH)
'			Print "mp3 length="+length
			Local sample:TAudioSample = CreateAudioSample(length,Music.SAMPLE_RATE,SF_STEREO16LE)
			Music.PlayStatus = 1
			Music.FillBuffer(Int(Sample.Samples),length*4)
			If Music.STREAM_SIZE<>Length
'				Print "GUESSED SIZE WAS WRONG! new size is "+Music.STREAM_SIZE
'				Print "resizing"
'				Print "old was "+sample.length
				Local sample1:TAudioSample = CreateAudioSample(Music.STREAM_SIZE,Music.SAMPLE_RATE,SF_STEREO16LE)
'				Print " new is "+sample1.length
				MemCopy(sample1.samples,sample.samples,Music.STREAM_SIZE*4)
				sample = sample1
'				Print "resize completed"
			EndIf
			Return Sample
		End Method
	End Type
	AddAudioSampleLoader New TAudioSampleLoaderMP3




	Rem
	bbdoc: Provides MP3 format streaming. (Windows only) 
	End Rem
	Type TMP3Stream Extends MaxMod_ACM
	
		Function Load:TMP3Stream(Url:Object)
	
			' make a new object instance --------------------------------------------------------------------
			Local This:TMP3Stream = New TMP3Stream
			This.Sample_Rate 			= 44100
			This.STREAM_FORMAT 			= MM_STEREO16
			This.File$ 					= String(URL)

			Local s:TStream 
			If TStream(url) 
				s = TStream(url)
			Else
				s = ReadFile(url)
			EndIf
			This.FpIn = s

'			SeekStream(s,StreamSize(s)-128)
'			Local tag$ = ReadString(s,3)
'			If tag$<>"TAG" Return Null
'			SeekStream(s,0)

			Local framesyncpos:Int, x:Int
			Local byte1:Int, byte2:Int, byte3:Int, byte4:Int, found:Int
			Repeat
				byte1 = ReadByte(s)
				If byte1 = %11111111
					byte2 = ReadByte(s)
					If (byte2&%11110000 = %11110000) And (byte2&%00000110)
						byte3 = ReadByte(s)
						If (byte3&%00001100) = %00001100 Then SeekStream(s,StreamPos(s)-2) ; Continue
						If ((byte3&%11110000) Shr 4)=%1111 Or (byte3&%11110000)=0 Then SeekStream(s,StreamPos(s)-2) ; Continue
						found = True
						Byte4 = ReadByte(s)
'						Print "found frame! @ "+(StreamPos(s)-4)
						Exit
					Else
						SeekStream(s,StreamPos(s)-1)
					EndIf
				EndIf
			Until Eof(s) Or StreamPos(s)>10000
			If Not found Return Null
'			Print "1 %"+Bin(byte1)
'			Print "2 %"+Bin(byte2)
'			Print "3 %"+Bin(byte3)
'			Print "4 %"+Bin(byte4)
			SeekStream(s,0)

			' AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM 
			Local a:Int = ((byte1&%11111111) Shl 3)|((byte2&%11110000) Shr 5)
			Local b:Int = (byte2&%00011000) Shr 3
			Local c:Int = (byte2&%00000110) Shr 1
			Local d:Int = (byte2&%00000001) 
			Local e:Int = (byte3&%11110000) Shr 4
			Local f:Int = (byte3&%00001100) Shr 2
			Local g:Int = (byte3&%00000010) Shr 1
			Local h:Int = (byte3&%00000001)
			Local i:Int = (byte4&%11000000) Shr 6
			Local j:Int = (byte4&%00110000) Shr 4
			Local k:Int = (byte4&%00001000) Shr 3
			Local l:Int = (byte4&%00000100) Shr 2
			Local m:Int = (byte4&%00000011)

			Local MPEGAudioVersionID:Int 	= b
'			Select MPEGAudioVersionID
'				Case MPEG1 	; Print "MPEG1"
'				Case MPEG2 	; Print "MPEG2"
'				Case MPEG25 ; Print "MPEG2.5"
'			EndSelect


			Local MPEGLayer:Int				= c
			Local MPEGProtected:Int			= d
			Local MPEGBitrate:Int			= e

			Local MPEGFrequency:Int			= f
			Local Freq:Int
			Select f
				Case %00
					Select MPEGAudioVersionID
						Case MPEG1 			; Freq = 44100 
						Case MPEG2 			; Freq = 22050 
						Case MPEG25 		; Freq = 11025 
					EndSelect
				Case %01
					Select MPEGAudioVersionID
						Case MPEG1 			; Freq = 48000 
						Case MPEG2 			; Freq = 24000 
						Case MPEG25 		; Freq = 12000 
					EndSelect
				Case %10
					Select MPEGAudioVersionID
						Case MPEG1 			; Freq = 32000
						Case MPEG2 			; Freq = 16000
						Case MPEG25 		; Freq = 8000
					EndSelect
			EndSelect
'			Print "frequency="+freq
			
			Local MPEGChannelMode:Int 		= i
			Local channels:Int
			Select i
				Case %00	; Channels = 2 ' Stereo
				Case %01 	; Channels = 2 ' Joint stereo (Stereo)
				Case %10 	; Channels = 2 ' Dual channel (2 mono channels)
				Case %11 	; Channels = 1 ' Single channel (Mono)
			EndSelect
'			Print "channels="+channels
	
			' define MP3 Input format -----------------------------------------------------------------------
			PokeShort	(this.informat,  0, WAVE_FORMAT_MPEGLAYER3)			' wFormatTag
			PokeShort	(this.informat,  2, Channels)						' nChannels
			PokeInt		(this.informat,  4, Freq)							' nSamplesPerSec    
			PokeInt		(this.informat,  8, Freq/4)							' nAvgBytesPerSec   
			PokeShort	(this.informat, 12, 1)								' nBlockAlign
			PokeShort	(this.informat, 14, 0)								' wBitsPerSample
			PokeShort	(this.informat, 16, MPEGLAYER3_WFX_EXTRA_BYTES)		' cbSize
			
			PokeShort	(this.informat, 18, MPEGLAYER3_ID_MPEG)				' wID
			If g=False
				PokeInt	(this.informat, 20, MPEGLAYER3_FLAG_PADDING_OFF)	' fdwFlags
			Else
				PokeInt	(this.informat, 20, MPEGLAYER3_FLAG_PADDING_ON)		' fdwFlags
			EndIf

'			PokeShort	(this.informat, 24, this.BLOCK_SIZE)				' nBlockSize
			If MPEGLayer=LayerI
				PokeShort	(this.informat, 24, 384)						' nBlockSize
				this.BLOCK_SIZE = 384
			Else
				PokeShort	(this.informat, 24, 1152)
				this.BLOCK_SIZE = 1152
			EndIf			
			
			
			PokeShort	(this.informat, 26, 1)								' nFramesPerBlock
			PokeShort	(this.informat, 28, 1393)							' nCodecDelay
		
			' define desired output format ------------------------------------------------------------------
			PokeShort	(this.outformat,  0, WAVE_FORMAT_PCM)				' wFormatTag
			PokeShort	(this.outformat,  2, 2)								' nChannels
			PokeInt		(this.outformat,  4, 44100)							' nSamplesPerSec    
			PokeInt		(this.outformat,  8, 44100*4 )						' nAvgBytesPerSec   
			PokeShort	(this.outformat, 12, 4)								' nBlockAlign
			PokeShort	(this.outformat, 14, 16)							' wBitsPerSample
			PokeShort	(this.outformat, 16, 0)								' cbSize
		
			' open the ACM conversion stream -----------------------------------------------------------------
			If Not this.StreamOpen() Return Null
			
			Return This
			
		EndFunction
	
	EndType

?